Conversation
Summary:
The React Native Babel preset replaces `Platform.select({...})` with the selected property during production transforms. JavaScript evaluates every object property initializer before calling `Platform.select`, so this can silently remove side effects from non-selected properties.
For example:
```js
Platform.select({
ios: selected(),
android: discarded(),
});
```
OR
```js
Platform.select({
ios() {
selected()
},
android: discarded(),
});
```
JavaScript would run both side effects, `selected()` and `discarded()`. The plugin's current behavior removes `discarded` on iOS however.
This fix preserves both side effects by testing for purity.
## Changelog:
[GENERAL] [FIXED] - Preserve side effects from discarded Platform.select property initializers in the Babel preset.
Test Plan: Added regression tests and ran existing tests, linter and formatter. Verified the ObjectMethod bailout case emits the source unchanged instead of invalid JS.
Differential Revision: D119484692
Pulled By: vzaidman
|
@vzaidman has exported this pull request. If you are a Meta employee, you can view the originating Diff in D119484692. |
|
@vzaidman Hello, I'd like to push back on this one a bit. Firstly, on the idea itself. I ran the branch through Metro to see what it actually does: It doesn't change anything for normal app code. Metro sets Dev and prod already behave the same. Metro's plugin runs in dev too, so This affects even RN itself. Two files import I understand the motivation, but the point of this plugin is to collapse platform branches. This check makes it quietly stop doing that for a whole class of ordinary code: any arm that is a property read, like Both examples above show the change can grow the bundle. The RN ones are also a correctness problem: the app ends up shipping and running code meant for another platform. So my ask is to not land this at all. Dropping the other platform's arm is the point of the inliner, not a bug. The only thing this changes today is that two RN files start shipping iOS code to Android. Btw, Expo has been doing the unconditional version of this. It turns Metro's plugin off and uses its own, which runs in prod only and collapses Thanks for your consideration! |
|
While we really need to consider all these points for the future of the plugin, in this point, we don't want to introduce breaking changes, so it has to be added back in. WDYT @robhogan ? |
|
Yeah, having looked a bit closer at what I don't think the plugin should collapse all branches regardless though. This isn't a plugin users opt into or even know about, necessarily, it's a release-mode optimisation on top of an API ( For a future version, maybe we should have some warning/feedback when we bail out of optimisations, and then we can ramp up the strictness? One for the DevX WG tomorrow..
Could you elaborate on that? Is that because this plugin catches more candidates than the Metro one does/did? |
Was previously executing both effects, but with the inliner, suddenly only one will be executed. This is a breaking change. Same goes for I agree that preferably we should collapse all these branches, but maybe with some sort of warning or other way of letting users know their code behaviour changes. Am I right that the inliner is enabled by default? If not then this is more trivial to do. |
There’s no previous version where we executed both selectors in release builds. Before RN 0.88, Metro inlined that pattern and elided Yeah, you could see this as a bug, but it’s a very longstanding behaviour, not a regression that needs an urgent fix, IIUC - not at the expense of the usefulness of the API. |
Summary: Reverts #58350, restoring the long-standing `Platform.select` inlining behavior. Per robhogan and vonovak: Metro has inlined Platform.select() (in release builds only, not configurable) forever really - it goes back to at least 2017, before the start of the Metro repo, eg: react/metro@a317b9d It's always had that behaviour where it collapses side-effects of inactive branches. Recently, the plugin was copied/re-implemented into RN. Right now (RN 0.88, Metro 0.87) they both have their own version of the plugin and Metro's runs after RN's, but Metro's shouldn't find anything left to inline and should be a no-op. I couldn't remove it from Metro immediately only because it'd need a major Metro release, but I plan to remove it soon. So RN's is effectively a continuation of Metro's 8 year old behaviour - we just moved the implementation. Changelog: [General][Fixed] - Restore long-standing Platform.select inlining behavior that collapses side effects of inactive branches See discussion: #58442 Differential Revision: D120147924
Summary: Pull Request resolved: #58541 Reverts #58350, restoring the long-standing `Platform.select` inlining behavior. Per robhogan and vonovak: Metro has inlined Platform.select() (in release builds only, not configurable) forever really - it goes back to at least 2017, before the start of the Metro repo, eg: react/metro@a317b9d It's always had that behaviour where it collapses side-effects of inactive branches. Recently, the plugin was copied/re-implemented into RN. Right now (RN 0.88, Metro 0.87) they both have their own version of the plugin and Metro's runs after RN's, but Metro's shouldn't find anything left to inline and should be a no-op. I couldn't remove it from Metro immediately only because it'd need a major Metro release, but I plan to remove it soon. So RN's is effectively a continuation of Metro's 8 year old behaviour - we just moved the implementation. Changelog: [General][Fixed] - Restore long-standing Platform.select inlining behavior that collapses side effects of inactive branches See discussion: #58442 Reviewed By: javache Differential Revision: D120147924 fbshipit-source-id: 0ec9cc7f8eade4e6276d0e049bc5fedfc613017f
Summary:
The React Native Babel preset replaces
Platform.select({...})with the selected property during production transforms. JavaScript evaluates every object property initializer before callingPlatform.select, so this can silently remove side effects from non-selected properties.For example:
OR
JavaScript would run both side effects,
selected()anddiscarded(). The plugin's current behavior removesdiscardedon iOS however.This fix preserves both side effects by testing for purity.
Changelog:
[GENERAL] [FIXED] - Preserve side effects from discarded Platform.select property initializers in the Babel preset.
Test Plan: Added regression tests and ran existing tests, linter and formatter. Verified the ObjectMethod bailout case emits the source unchanged instead of invalid JS.
Differential Revision: D119484692
Pulled By: vzaidman